ALGORITHMES GLOUTONS

Afin de travailler sur la notion d'algorithme glouton, nous allons partir d'un cas d'étude : le monnayeur.

Exemple: le rendu de monnaie, le monnayeur

Problème

On dispose des pièces de monnaie correspondant aux valeurs a0,,an1{a_0, \ldots, a_{n-1}}, avec 1=a0<a2<an11=a_0 < a_2 \ldots < a_{n-1}. Pour chaque valeur le nombre de pièces est non borné.

Étant donnée une quantité cc entière, on veut trouver une façcon de "rendre" la somme cc avec un \textcolor{red}{nombre de pièces minimum}.

Une instance du problème est la donnée des valeurs faciales des pièces a0,,an1{a_0, \ldots, a_{n-1}}, avec 1=a0<a2<an11=a_0 < a_2 \ldots < a_{n-1} et de la somme cc à payer.

Une solution est pour chaque type de pièce ( 0in10 \le i \le n-1) un nombre de pièces nbinb_i telle que i=0n1nbiai=c\sum_{i=0}^{n-1} nb_i* a_i =c: on a bien payé cc (exactement).

Elle est optimale si i=0n1nbi\sum_{i=0}^{n-1} nb_i est minimal : on a minimisé la fonction objectif, ici le nombre total de pièces.

Une instance

Solution

Cette solution est-elle optimale ? Peut-on faire mieux ? Pourquoi ? Comment justifier que la solution est optimale ?

Preuve par cas?

Donc c'est optimal !

L'algorithme

En pseudo code

// somme à payer entière >0 
// valeurs des pièces: $1=a[0] < ...< a[n-1]$
// les a[i] sont les valeurs de pièces triés en croissant 
int nb_pieces=0; //solution vide  
int[n] nb;  //initialisé à 0 
for (int i=n−1;i >= 0;i--) do
    nb[i] = somme/a[i] ;
    nb_pieces = nb_pieces + nb[i];
    somme = somme modulo a[i];
end

En python

# somme à payer entière >0 
# La liste ListeMontants est triée par montant croissant
def money(somme,ListeMontants) :
    ListeNbPieces=[0 for x in ListeMontants]
    for k in range(len(ListeMontants),0,-1) :
        ListeNbPieces[k]=somme//ListeMontants[k]
        somme=somme%ListeMontants[k]
    return somme,ListeNbPieces

L'algorithme est-il correct?

Exemple 1

Par exemple, soit 26 à payer en pièces de 7, 6, 1. L'algorithme glouton répond 3 Pièces de 7, 5 pièces de 1

La solution optimale est 2 pièces de 7, 2 pièces de 6

Donc, Non, l'algo n'est pas correct!

Exemple 2

Encore un plus petit exemple : soit le système de pièces (1, 3, 4), la somme de 6 à payer.

Le glouton rendra 4 puis 1 puis 1 donc 3 pièces alors qu'on peut rendre 3 et 3 soit 2 pièces,

Exemple 3

Soit un ancien système britannique: 1, 2, 6, 12, 24, 30, 60, 240

Soit la somme à payer de 48.

L'algorithme glouton était-il optimal?

A vous de jouer !

Pour rendre 48, l'algorithme glouton n'est pas optimal.

Système canonique

On appelle canonique un système de pièces pour lequel l'algorithme glouton est optimal. Les systèmes actuels le sont (presque?) tous.

p := [0, 0, ..., 0]
pour chaque i de 1 à  n:
    tant que v[i] <= S:
        p[i] := p[i] + 1
        S := S - v[i]

Quand est-il correct?

Par exemple, est-il toujours correct pour 1,2,5,10,201,2, 5,10,20 ?

La solution produite par l'algorithme glouton est

Que dire de l'optimale?

Soit o20,o10,o5,o2,o1o_{20},o_{10},o_5,o_2,o_1 une solution optimale. On a c=20o20+10o10+5o5+2o2+o1c=20*o_{20}+10*o_{10}+5*o_{5}+2*o_{2}+o_1

Alors, o1<2o_1<2, sinon on peut remplacer 22 pièces de 11 par une de 22.

De même, o2<3o_2<3, sinon, on remplace 3 pièces de 22 par une de 55 et une de 11.

De plus si o2=2o_2=2, alors o1=0o_1=0, sinon on remplace deux de 22 et une de 11 par une de 55;

o5<2o_5<2, sinon, on remplace deux de 55 par une de 1010.

o10<2o_{10}<2, sinon, on remplace deux de 1010 par une de 2020.

Que dire de l'optimale?

10o10+5o5+2o2+o1<=10+5+4<2010*o_{10}+5*o_{5}+2*o_{2}+o_{1}<=10+5+4<20

c=20o20+10o10+5o5+2o2+o1c=20*o_{20}+10*o_{10}+5*o_{5}+2*o_{2}+o_1.

Donc o20=c÷ 20=g20o_{20}= c \div \ 20= g_{20} et 10o10+5o5+2o2+o1=c mod 2010*o_{10}+5*o_{5}+2*o_{2}+o_1=c \ mod \ 20.

De même, 5o5+2o2+o1<105*o_5+2*o_{2}+o_1<10 donc o10=c mod 20÷ 10=g10o_{10}= c \ mod \ 20 \div \ 10= g_{10}.

Donc 5o5+2o2+o1=c mod 105*o_5+2*o_{2}+o_1=c\ mod \ 10;

Comme 2o2+o1<52*o_{2}+o_1<5, o5=c mod10÷5=g5o_5=c \ mod 10 \div 5= g_5 et 2o2+o1=c mod 52*o_{2}+o_1=c \ mod \ 5

De même, o2=(c mod 5)÷ 2=g2,o1=c mod 5 mod2=g1o_2=(c\ mod \ 5 )\div \ 2 =g_2, o_1= c \ mod \ 5 \ mod 2 = g_1.

Le glouton est-il optimal?

Pour ce système 1,2,5,10,201,2,5,10,20, la solution gloutonne est (l'unique) optimale.

Pour le système 1,2,5,10,20,501,2,5,10,20,50, la solution gloutonne est-elle optimale?

Principe général d'un algorithme glouton

Généralités

Les algorithmes dits gloutons (en anglais greedy algorithm) servent à résoudre certains problèmes d'optimisation.

Un problème d'optimisation : on cherche à construire une solution à un problème qui optimise une fonction objectif. Un problème d'optimisation se définit comme :

On peut utiliser un algorithme d’approximation pour résoudre le problème d'optimisation : il fournit toujours une solution mais pas forcément une solution optimale. Bien sûr, on souhaite qu’il soit efficace.

Un algorithme d’approximation peut être:

Le principe d’une méthode gloutonne :

On procède de façon séquentielle, en faisant à chaque étape le choix qui semble localement le meilleur.

Dans certains cas, cela donnera finalement la meilleure solution : on parlera d’algorithmes gloutons exacts.

Dans d’autres, non, on parlera d’heuristiques gloutonnes.

En général, le fait que le résultat soit correct est facile, le fait qu'il soit optimal n'est pas évident.

Le schéma de la méthode gloutonne

Il est basé sur un critère local de sélection des éléments de EE pour construire une solution optimale. En fait, on travaille sur l’objet "solution partielle" - "début de solution"- et on doit disposer de :

Schémas d’algo glouton

// on initialise l’ensemble des "briques"
// élémentaires des solutions.
Ens.init() ;
// on initialise la solution :
// ensemble (ou suite) "vide" ou..
Sol.Init() ;
while (Non Sol.complete ?() et Ens.NonVide ?()) do
    //on choisit x selon critère glouton
    x ← Ens.select();
    if Sol.ajoutPossible(x) then
        Sol.ajout(x) ; fsi ;
    //dans certains problèmes, toujours le cas
    if CertainesConditions then
        Ens.retirer(x) ;
    // selon les cas, x considéré une fois ou plus
end
// la Solution partielle est a priori complète
return Sol ;

Autre schéma :

Algorithme vorace
Début
S <- EnsembleVide
C <- ensemble des candidats à la solution
Tant que S n’est pas une solution et C <> EnsembleVide Faire
    x <- choisir un élément de C le plus prometteur
    C <- C - x
    Si realisable(solution,x) Alors
        solution <- union(solution, x)
    Finsi
FinTantQue
Si S est une solution Alors
    Retourner S
Sinon
    Retourner pas de solution
FinSi

Pour sélectionner, on trie souvent tout simplement la liste des éléments selon le critère glouton au départ ; on balaye ensuite cette liste dans l’ordre.

Ceci est un schéma général qui a l’avantage et les inconvénients d’un schéma : dans certains cas, c’est encore plus simple ! par exemple, lorsque la solution recherchée est une permutation, en général l’algorithme se réduit au tri selon le critère glouton ! dans d’autres cas, les “solutions” sont un peu plus compliquées et on a besoin d’un schéma un peu plus sophistiqué.